Micron Document
rns.moscow 🟥 [git]


Commit bbfb1beaecbe9c14fbb83624a2e15f70c42656b6


Parents : 37909a5
Author : Nickie Deuxyeux <nikolay@dvoeglazov.ru>
Date : 2026-07-13T23:59:57+03:00

Revert "Reapply "Add universal nRF52 erase automation and fix display-apply reboot/reliability issues""

This reverts commit dfd5ada251b0d923eeb79c79c232f2e3957ea5e8.

Changes

2 files changed, 22 insertions(+), 131 deletions(-)

M htdocs/index.html +19 -124

Diff

diff --git a/htdocs/index.html b/htdocs/index.html
index 5d43d4f..d59aea2 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -788,22 +788,6 @@ const MR = '/firmware/microreticulum/latest/';
const RT = '/firmware/rtnode/latest/';
const MSH = '/firmware/meshtastic/latest/';
const MC = '/firmware/meshcore/latest/';
-const ERASE = '/firmware/erase/latest/';
-
-// USB VIDs used by nRF52 boards in both application and bootloader mode
-// (Adafruit, Nordic, Seeed). Shared by DFU-mode detection/re-enumeration
-// and by the platform check in eraseFlash().
-const NRF52_VIDS = [0x239a, 0x1915, 0x2886];
-
-// A small non-interactive app (github: see ~/Development/nRF52-Eraser) that
-// wipes the internal LittleFS settings/bonding partition on any
-// Adafruit-bootloader nRF52840 board and reboots straight back into the UF2
-// bootloader. Previously-flashed firmware (Meshtastic, RNode, etc.) can
-// leave that partition in a state that prevents a later firmware from
-// booting cleanly, so flashAll() always erases it first for nRF52 boards,
-// and eraseFlash() flashes it directly when the erase button is used on an
-// nRF52 device.
-const NRF52_ERASER_ZIP = ERASE + 'nRF52_Universal_Eraser_DFU.zip';
function fc(name, sz, boot0) {
return {
@@ -1670,6 +1654,7 @@ Vue.createApp({
await Utils.sleepMillis(200);
// ── Phase 2a: VID-based nRF52 detection (skip esptool entirely) ────
+ const NRF52_VIDS = [0x239a, 0x1915, 0x2886];
const portInfo = port.getInfo();
if (NRF52_VIDS.includes(portInfo.usbVendorId)) {
this.detectedChip = 'nRF52';
@@ -1871,49 +1856,7 @@ Vue.createApp({
this.addLog('ok', ' ✓ ', 'Firmware downloaded');
this.flashProgress = 5;
- // 2. nRF52 boards: wipe the internal filesystem/settings partition
- // before flashing, in case a previous firmware (Meshtastic,
- // RNode, etc.) left it in a state that could prevent the new
- // firmware from booting cleanly. Skipped if the device is
- // already running RNode firmware — it manages that same
- // filesystem/EEPROM region itself, so there's no vendor cruft to
- // clear. We can't otherwise inspect flash contents to decide:
- // the legacy serial DFU protocol the bootloader speaks is
- // write-only, so once the device is in bootloader mode there's
- // no way to check — this probe has to happen in application
- // mode, before we touch the bootloader at all.
- if (variant.platform === 'nrf52') {
- let alreadyRNode = false;
- this.addLog('cmd', '$ ', 'Checking for existing RNode firmware...');
- try {
- const probe = await RNode.fromSerialPort(port);
- alreadyRNode = await probe.detect();
- await probe.close();
- } catch(e) {
- try { await port.close(); } catch(ex) {}
- }
-
- if (alreadyRNode) {
- this.addLog('ok', ' ✓ ', 'RNode firmware already present, skipping erase...');
- } else {
- this.addLog('cmd', '$ ', 'Erasing nRF52 internal filesystem to clear any leftover data from a previous firmware...');
- this.currentStep = this.t('step.erasing');
- const eraseBlob = await this.fetchFirmwareBlob(NRF52_ERASER_ZIP);
- await this.doNrf52Flash(port, eraseBlob, this.t('step.erasing'));
- this.addLog('ok', ' ✓ ', 'Flash erased');
- this.flashProgress = 5;
-
- // The eraser app reboots the device straight back into the
- // bootloader — give the OS a moment to tear down and recreate
- // the USB serial node before immediately touching/flashing it
- // again, otherwise the 1200-baud touch below silently fails to
- // even open the (not yet recreated) port.
- this.addLog('info', ' > ', 'Waiting for device to settle after erase...');
- await Utils.sleepMillis(2000);
- }
- }
-
- // 3. Flash — dispatch by platform
+ // 2. Flash — dispatch by platform
let flashStep;
if (variant.platform === 'nrf52') {
const binName = variant.firmware_zip.split('/').pop().replace(/\.zip$/i, '.bin');
@@ -1930,10 +1873,9 @@ Vue.createApp({
await this.doEsp32Flash(port, blob, variant.flash_config, flashStep);
} catch(e) {
if (e.message && /open|port|access/i.test(e.message)) {
- this.addLog('warn', ' ! ', `Port error: ${e.message}`);
- this.addLog('cmd', '$ ', 'Attempting bootloader reset...');
+ this.addLog('warn', ' ! ', `port error: ${e.message} — Attempting bootloader reset...`);
await this.enterBootloader(port);
- this.addLog('cmd', '$ ', 'Retrying flash...');
+ this.addLog('cmd', '$ ', 'retrying flash...');
await this.doEsp32Flash(port, blob, variant.flash_config, flashStep);
} else {
throw e;
@@ -1942,7 +1884,7 @@ Vue.createApp({
}
this.flashProgress = 90;
- // 4. Post-flash RNode config
+ // 3. Post-flash RNode config
if (variant.needs_provisioning) {
this.addLog('cmd', '$ ', 'Waiting for device to boot...');
this.currentStep = this.t('step.rebooting');
@@ -1993,7 +1935,7 @@ Vue.createApp({
await this.provisionDevice(rnode, device.product_id, variant.mapped_model_id ?? variant.model_id);
this.addLog('ok', ' ✓ ', 'EEPROM provisioned');
} else {
- this.addLog('ok', ' ✓ ', 'EEPROM already provisioned, skipping...');
+ this.addLog('info', ' > ', 'EEPROM already provisioned, skipping');
}
this.flashProgress = 97;
@@ -2066,7 +2008,6 @@ Vue.createApp({
} finally {
this.isWorking = false;
this.currentStep = '';
- this.flashProgress = 0;
}
},
@@ -2082,12 +2023,13 @@ Vue.createApp({
await new Nrf52DfuFlasher(port).enterDfuMode();
} catch(e) {
// couldn't touch (port busy/gone) — try flashing as-is
- this.addLog('info', ' > ', `Touch skipped: ${e.message || e}`);
+ this.addLog('info', ' > ', `touch skipped: ${e.message || e}`);
return port;
}
// wait for the device to come back, trying the original port first,
// then any other granted nRF52 port (bootloader may have a new PID)
+ const NRF52_VIDS = [0x239a, 0x1915, 0x2886];
for (let attempt = 0; attempt < 16; attempt++) {
const granted = (await navigator.serial.getPorts())
.filter(p => p !== port && NRF52_VIDS.includes(p.getInfo().usbVendorId));
@@ -2097,7 +2039,7 @@ Vue.createApp({
await p.close();
if (p !== port) {
const { usbVendorId: v, usbProductId: pr } = p.getInfo();
- this.addLog('info', ' > ', `Bootloader re-enumerated as VID 0x${v?.toString(16).padStart(4,'0')} PID 0x${pr?.toString(16).padStart(4,'0')}`);
+ this.addLog('info', ' > ', `bootloader re-enumerated as VID 0x${v?.toString(16).padStart(4,'0')} PID 0x${pr?.toString(16).padStart(4,'0')}`);
this.connectedPort = p;
}
return p;
@@ -2151,7 +2093,7 @@ Vue.createApp({
for (let attempt = 0; attempt < 3 && !flashed; attempt++) {
if (attempt > 0) {
this.progressLineIdx = -1;
- this.addLog('info', ' > ', `Port not ready, retrying in 1 s... (${attempt + 1}/3)`);
+ this.addLog('info', ' > ', `port not ready, retrying in 1 s... (${attempt + 1}/3)`);
await Utils.sleepMillis(1000);
lastPct = -1;
}
@@ -2224,7 +2166,7 @@ Vue.createApp({
const chip = await esploader.main();
this.addLog('ok', ' ✓ ', `Chip: ${chip}`);
- this.addLog('cmd', '$ ', `Flashing firmware (${flashConfig.flash_size})...`);
+ this.addLog('cmd', '$ ', `flashing ${flashConfig.flash_size} flash...`);
this.currentStep = flashStep;
let lastPct = 0;
@@ -2255,7 +2197,7 @@ Vue.createApp({
});
this.progressLineIdx = -1;
- this.addLog('ok', ' ✓ ', 'Firmware flashed');
+ this.addLog('ok', ' ✓ ', 'Firmware flashed!');
this.addLog('cmd', '$ ', 'Resetting device...');
let flashFinishOk = false;
@@ -2374,23 +2316,11 @@ Vue.createApp({
},
// ── Advanced tools ────────────────────────────────────────
- // Erases the connected board's flash. Branches by platform: nRF52
- // boards don't support a raw esptool-style chip erase over DFU, so
- // instead this puts the board in bootloader mode and flashes the
- // universal eraser app (NRF52_ERASER_ZIP), which wipes the internal
- // filesystem/settings partition and reboots back into the bootloader.
async eraseFlash() {
+ if (!window.ESPLoader) { alert('esptool-js not loaded'); return; }
const port = await this.requestPort();
if (!port) return;
- const { usbVendorId } = port.getInfo();
- if (NRF52_VIDS.includes(usbVendorId)) {
- await this.eraseNrf52Flash(port);
- return;
- }
-
- if (!window.ESPLoader) { alert('esptool-js not loaded'); return; }
-
this.isWorking = true;
this.flashProgress = 0;
this.clearLog();
@@ -2414,10 +2344,9 @@ Vue.createApp({
chip = await esploader.main();
} catch(e) {
if (e.message && /open|port|access/i.test(e.message)) {
- this.addLog('warn', ' ! ', `Port error: ${e.message}`);
- this.addLog('cmd', '$ ', 'Attempting bootloader reset...');
+ this.addLog('warn', ' ! ', `port error: ${e.message} — attempting bootloader reset...`);
await this.enterBootloader(port);
- this.addLog('cmd', '$ ', 'Retrying connection...');
+ this.addLog('cmd', '$ ', 'retrying connection...');
chip = await esploader.main();
} else {
throw e;
@@ -2425,7 +2354,7 @@ Vue.createApp({
}
this.addLog('ok', ' ✓ ', `Chip: ${chip}`);
await esploader.eraseFlash();
- this.addLog('ok', ' ✓ ', 'Flash erased');
+ this.addLog('ok', ' ✓ ', 'Flash erased!');
this.statusType = 'success'; this.statusMsg = this.t('status.erased');
} catch(e) {
this.addLog('err', ' ✗ ', String(e.message || e));
@@ -2437,33 +2366,7 @@ Vue.createApp({
} catch(e) {}
try { await port.close(); } catch(e) {}
this.disconnectPort();
- this.isWorking = false; this.currentStep = ''; this.flashProgress = 0;
- }
- },
-
- // nRF52 erase path used by eraseFlash() above. Puts the device in
- // bootloader mode (via doNrf52Flash -> ensureNrf52Bootloader) and
- // flashes the universal eraser app.
- async eraseNrf52Flash(port) {
- this.isWorking = true;
- this.flashProgress = 0;
- this.clearLog();
- this.statusMsg = '';
- this.currentStep = this.t('step.erasing');
- this.addLog('cmd', '$ ', 'Erasing nRF52 internal filesystem via universal eraser...');
-
- try {
- const eraseBlob = await this.fetchFirmwareBlob(NRF52_ERASER_ZIP);
- await this.doNrf52Flash(port, eraseBlob, this.t('step.erasing'));
- this.addLog('ok', ' ✓ ', 'Flash erased');
- this.statusType = 'success'; this.statusMsg = this.t('status.erased');
- } catch(e) {
- this.addLog('err', ' ✗ ', String(e.message || e));
- this.statusType = 'error'; this.statusMsg = String(e.message || e);
- } finally {
- try { await port.close(); } catch(e) {}
- this.disconnectPort();
- this.isWorking = false; this.currentStep = ''; this.flashProgress = 0;
+ this.isWorking = false; this.currentStep = '';
}
},
@@ -2565,7 +2468,7 @@ Vue.createApp({
const productId = device.product_id;
const modelId = variant.mapped_model_id ?? variant.model_id;
- this.addLog('cmd', '$ ', `Provisioning EEPROM (Product: 0x${productId.toString(16)}, Model: 0x${modelId.toString(16)})...`);
+ this.addLog('cmd', '$ ', `Provisioning EEPROM (product: 0x${productId.toString(16)}, model: 0x${modelId.toString(16)})...`);
this.currentStep = this.t('step.provisioning');
await this.provisionDevice(rnode, productId, modelId);
this.addLog('ok', ' ✓ ', 'EEPROM provisioned');
@@ -2597,7 +2500,7 @@ Vue.createApp({
this.addLog('cmd', '$ ', 'Reading firmware hash from device...');
this.currentStep = this.t('step.hashing');
const hash = await rnode.getFirmwareHash();
- this.addLog('info', ' > ', `Hash: ${Utils.bytesToHex(hash)}`);
+ this.addLog('info', ' > ', `hash: ${Utils.bytesToHex(hash)}`);
await rnode.setFirmwareHash(hash);
await rnode.getRom(30000);
this.addLog('ok', ' ✓ ', 'Firmware hash set');
@@ -2838,16 +2741,8 @@ Vue.createApp({
async applyDisplay() {
await this.withRNode(this.t('step.applying_display'), async (rnode) => {
this.addLog('cmd', '$ ', 'applying display settings...');
- // Rotation last: it's the only field that intentionally reboots the
- // device (drot_conf_save() only when the value actually changes),
- // so applying intensity/timeout first means they land before that
- // reboot. Small gaps between writes are cheap insurance against
- // nRF52's eeprom_update() (a full LittleFS close+reopen per byte)
- // being hit as a rapid back-to-back burst.
await rnode.setDisplayIntensity(this.dispIntensity);
- await Utils.sleepMillis(150);
await rnode.setDisplayTimeout(this.dispTimeout);
- await Utils.sleepMillis(150);
await rnode.setDisplayRotation(this.dispRotation);
this.addLog('ok', ' ✓ ', 'display settings applied and saved');
this.statusType = 'success'; this.statusMsg = this.t('status.display_applied');

diff --git a/htdocs/js/nrf52_dfu_flasher.js b/htdocs/js/nrf52_dfu_flasher.js
index a05cb1b..30ddbe3 100644
--- a/htdocs/js/nrf52_dfu_flasher.js
+++ b/htdocs/js/nrf52_dfu_flasher.js
@@ -50,21 +50,17 @@ class Nrf52DfuFlasher {
/**
* Reopens the serial port after a USB disconnect (erase or page-write re-enum).
- * Retries open() every 500 ms for up to 10 seconds — a full application
- * DFU start triggers a real flash-page erase (much bigger than the
- * eraser app's own tiny footprint) which can drop the USB connection
- * for longer than the previous 4-second budget, especially right after
- * the device already went through one erase+reboot cycle moments ago.
+ * Retries open() every 500 ms for up to 4 seconds.
*/
async waitAndReconnect() {
try { await this.serialPort.close(); } catch(e) {}
- for (let attempt = 0; attempt < 20; attempt++) {
+ for (let attempt = 0; attempt < 8; attempt++) {
await this.sleepMillis(500);
try {
await this.serialPort.open({ baudRate: this.FLASH_BAUD });
return;
} catch(e) {
- if (attempt === 19) throw new Error('bootloader did not reconnect after USB drop');
+ if (attempt === 7) throw new Error('bootloader did not reconnect after USB drop');
}
}
}

Served by rngit 1.4.2 - Generated in 0.03s